/*
    Simple Greedy Snake game
    Copyright (C) 2021 AKCodeDev

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* Made by CodingJellyfish */

#define WIDTH       20
#define HEIGHT      15
#define DIFFICULTY  0.03
#define LEN         3
#define TIME        200
#define LIMIT       120
#define PLUS        20
#define WAIT        15
#define UP          'w'
#define DOWN        's'
#define LEFT        'a'
#define RIGHT       'd'

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#ifdef _WIN32
    #include <conio.h>
    #include <windows.h>
#elif __unix__
    #include <fcntl.h>
    #include <termios.h>
    #include <unistd.h>
#endif

void clearFunc()
{
#ifdef _WIN32
    system("CLS");
#elif __unix__
    system("clear");
#endif
}

int ifKeyboardHit()
{
#ifdef _WIN32
    return kbhit();
#elif __unix__

    struct termios oldt, newt;
    char ch;
    int oldf;

    tcgetattr(STDIN_FILENO, &oldt);

    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);

    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    ch = getchar();

    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    if(ch != EOF)
    {
        ungetc(ch, stdin);
        return 1;
    }
    return 0;

#endif
}

char getKeyboardHitChar()
{
#ifdef _WIN32

    return getch();

#elif __unix__

    struct termios oldt, newt;
    char ch;
    int oldf = 0;
 
    tcgetattr(oldf, &newt);

    oldt = newt;
    cfmakeraw(&newt);
    tcsetattr(oldf, TCSANOW, &newt);

    ch = getchar();
    tcsetattr(oldf, TCSANOW, &oldt);

    return ch;

#endif
}

void printYellow()
{
#ifdef _WIN32
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xe);
#elif __unix__
    printf("\033[32m");
#endif
}

void printGreen()
{
#ifdef _WIN32
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xa);
#elif __unix__
    printf("\033[33m");
#endif
}

void printCyan()
{
#ifdef _WIN32
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xb);
#elif __unix__
    printf("\033[36m");
#endif
}

void resetColor()
{
#ifdef _WIN32
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0xf);
#elif __unix__
    printf("\033[0m");
#endif
}

enum Status {
    NONE,
    SNAKE,
    WALL,
    BONUS,
    BONUS2
} map[HEIGHT+2][WIDTH+2];

enum Event {
    NO,
    QUIT
};

struct Node {
    int x, y;
};
struct Node snake_body[WIDTH*HEIGHT], *snake_end=snake_body+LEN+1;
struct Node obstacle_body[WIDTH*HEIGHT], *obstacle_end=obstacle_body;

struct Node bonus, bonus2;

void       init();
enum Event render();

void       setBorder();
void       initObstacles();
void       initSnake();
int        moveSnake();
void       setObstacles();
void       setBonus();
void       setBonus2();
void       resetBonus();
void       resetBonus2();
void       printMap();
void       resetMap();

char input, pre_input;
int score, wait,valid;
time_t start_time, end_time=LIMIT;

int main() {
    init();
    while(1) {
        clock_t stime = clock();
        pre_input = input;
        while(clock() - stime <= TIME) {
            if(ifKeyboardHit()) {
                input = getKeyboardHitChar();
				if(!start_time && (input==UP||input==DOWN||input==LEFT||input==RIGHT))
					start_time=time(NULL), wait = time(NULL);
			}
        }
        if(render() == QUIT)
            break;
    }
    return 0;
}

void init() {
    srand(time(NULL));
    setBorder();
    initObstacles();
    initSnake();
    resetBonus();
}

enum Event render() {
    setObstacles();
    setBonus();
    setBonus2();
    int ret = moveSnake();
    printMap();
    resetMap();
    if(ret)
        return NO;
    else
        return QUIT;
}

void setBorder() {
    int i;
    for(i = 0; i <= WIDTH+1; i ++)
        map[0][i] = map[HEIGHT+1][i] = WALL;
    for(i = 0; i <= HEIGHT+1; i ++)
        map[i][0] = map[i][WIDTH+1] = WALL;
}

void initObstacles() {
    int i;
    for(i = 1; i <= WIDTH*HEIGHT*DIFFICULTY; i ++) {
        do {
            obstacle_end->x = rand() % HEIGHT + 1;
            obstacle_end->y = rand() % WIDTH + 1;
        }while(map[obstacle_end->x][obstacle_end->y] != NONE);
        obstacle_end ++;
    }
}

void initSnake() {
    do {
        snake_body->x = rand() % HEIGHT + 1;
        snake_body->y = rand() % WIDTH + 1;
    }while(map[snake_body->x][snake_body->y] != NONE);
}

int moveSnake() {
    struct Node* i;
    for(i = snake_end - 2; i >= snake_body; i --)
        if(i->x || i->y)
            map[i->x][i->y] = SNAKE;
    switch(input) {
        case UP: if(pre_input != DOWN)snake_body->x --;else snake_body->x ++; break;
        case DOWN: if(pre_input != UP)snake_body->x ++;else snake_body->x --; break;
        case RIGHT: if(pre_input != LEFT)snake_body->y ++;else snake_body->y --; break;
        case LEFT: if(pre_input != RIGHT)snake_body->y --;else snake_body->y ++; break;
        default:
            map[snake_body->x][snake_body->y] = NONE;
    }
    if(end_time==time(NULL)-start_time)
		return 0;
    switch(map[snake_body->x][snake_body->y]) {
        case WALL: 
        case SNAKE: return 0; break;
        case BONUS: resetBonus(); snake_end++; break;
        case BONUS2: valid=0; if(!wait)wait=time(NULL); end_time+=PLUS; break;
        case NO: break;
    }
    if(wait && time(NULL)-wait>=WAIT)resetBonus2(),wait=0;
    score = snake_end - snake_body - 1;
    map[snake_body->x][snake_body->y] = SNAKE;
    for(i = snake_end - 1; i > snake_body; i --)
        *i = *(i-1);
    return 1;
}

void setObstacles() {
    struct Node* i;
    for(i = obstacle_end - 1; i >= obstacle_body; i --)
        map[i->x][i->y] = WALL;
}

void resetBonus() {
    do {
        bonus.x = rand() % HEIGHT + 1;
        bonus.y = rand() % WIDTH + 1;
    }while(map[bonus.x][bonus.y] != NONE);
}

void setBonus() {
    map[bonus.x][bonus.y] = BONUS;
}

void resetBonus2() {
	valid=1;
    do {
        bonus2.x = rand() % HEIGHT + 1;
        bonus2.y = rand() % WIDTH + 1;
    }while(map[bonus2.x][bonus2.y] != NONE);
}

void setBonus2() {
	if(valid)
		map[bonus2.x][bonus2.y] = BONUS2;
}

void printMap() {
    clearFunc();
    int i, j;
    for(i = 0; i <= HEIGHT+1; i ++) {
        for(j = 0; j <= WIDTH+1; j ++) {
            switch(map[i][j]) {
                case NONE:
                    printf("  ");
                    break;
                case WALL:
                    printf("[]");
                    break;
                case SNAKE:
                    printGreen(),printf("()"),resetColor();
                    break;
                case BONUS:
                    printYellow(),printf("<>"),resetColor();
                    break;
                case BONUS2:
                    printCyan(),printf("++"),resetColor();
                    break;
            }
        }
        putchar('\n');
    }
    printf("Length: %d  Time: %lu", score, start_time?end_time-time(NULL)+start_time:LIMIT);
}

void resetMap() {
    int i, j;
    for(i = 1; i <= HEIGHT; i ++) {
        for(j = 1; j <= WIDTH; j ++) {
            map[i][j] = NONE;
        }
    }
}
